﻿using System;
using System.Collections.Specialized;
using System.ComponentModel.Design;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Web.UI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ASP = System.Web.UI.WebControls;
using System.Reflection;
using System.ComponentModel;

namespace JQuery.Net.UI
{
    [ControlBuilder(typeof(ControlBuilder))]
    [ParseChildren(true), PersistChildren(false)]
    public abstract class WebControl
        : ASP.WebControl
        , IjQNetAPI
        , IPostBackDataHandler
        , ICallbackEventHandler
    {
        #region "constructors"
        public WebControl()
            : base() { Initialize(); }
        public WebControl(HtmlTextWriterTag tag)
            : base(tag) { Initialize(); }
        public WebControl(string tag)
            : base(tag) { Initialize(); }
        #endregion

        #region "properties"
        protected jQNetArguments Arguments { get; set; }
        protected Templates RenderTemplates
        {
            get
            {
                if (templates == null)
                {
                    templates = new Templates(this);
                }
                return templates;
            }
        }
        
        public virtual List<Script> Templates { get; set; }
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public List<Script> ScriptEvents { get; set; }
        [Browsable(false)]
        public override bool EnableViewState
        {
            get
            {
                return base.EnableViewState;
            }
            set
            {
                base.EnableViewState = value;
            }
        }
        #endregion

        #region "IJQueryControl Members"
        /// <summary>
        /// Register the Script Includes that are required for the UI Core.
        /// </summary>
        /// <param name="manager"></param>
        /// <param name="options"></param>
        public virtual void RegisterScriptIncludes(ResourceManager manager, JQNRegistration options)
        {

        }

        public abstract void RegisterScriptStartup(StringWriter writer);

        #endregion

        #region "ICallbackEventHandler Members"
        public string GetCallbackResult()
        {
            string result = null;

            switch (Arguments.ResultData.Type)
            {
                case JQNDataTypes.xml:
                    Page.Response.ContentType = "text/xml";

                    result = String.Format("<result><canceled>{0}</canceled><data>{1}</data></result>", Arguments.Cancel, Arguments.ResultData.Data);
                    break;
                default:
                    Page.Response.ContentType = "text/x-json";

                    result = JsonConvert.SerializeObject(new
                    {
                        canceled = Arguments.Cancel,
                        type = Enum.GetName(typeof(JQNDataTypes), Arguments.ResultData.Type),
                        data = Arguments.ResultData.Data
                    }); break;
            }

            return result;
        }

        public void RaiseCallbackEvent(string arguments)
        {
            var JSON = JObject.Parse(arguments);

            Arguments = new JQNArguments()
            {
                Event = JSON["event"].Type == JTokenType.String
                    ? (JQNEvents)Enum.Parse(typeof(JQNEvents), (string)JSON["event"], true)
                    : JSON["event"].Type == JTokenType.Integer
                        ? ((JQNEvents)(int)JSON["event"])
                        : JQNEvents.unknown,
                Arguments = (JObject)JsonConvert.DeserializeObject(JSON["args"].ToString())
            };

            if (postdata_loaded == false)
            {   /// known bug in asp.net under certain conditions the IPostBackDataHandler interface may not have been processed.
                postdata_loaded = LoadPostData(UniqueID, Page.Request.Form);
            }

            if (postdata_loaded && Arguments.Event == JQNEvents.change)
            {   /// data has changed let's raise the change event. 
                /// we still use the same interface for asp.net.
                RaisePostDataChangedEvent();

                return; /// we are done
            }
            else if (Arguments.Event == JQNEvents.template)
            {
                Arguments.ResultData.Type = JQNDataTypes.html;

                var id = ((string)Arguments.Arguments["id"]).Substring(1);

                if (!(Arguments.Cancel = RenderTemplates[id] == null))
                {
                    using (StringWriter html = new StringWriter())
                    using (HtmlTextWriter writer = new HtmlTextWriter(html))
                    {
                        RenderTemplates[id].RenderControl(writer);

                        Arguments.ResultData.Data = html.ToString().Regex("[\n\r\t]", String.Empty);
                    }
                }

                return; /// we are done
            }
            else if (Arguments.Event != JQNEvents.change)
            {   /// all other Callback events are processed here.
                var handler = (JQNHandler)Events[callback_key];

                Arguments.Cancel = handler == null;

                if (Arguments.Cancel == false)
                {
                    handler(this, Arguments);
                }

                return; /// we are done
            }
        }
        #endregion

        #region "IPostBackDataHandler Members"
        public bool LoadPostData(string postDataKey, NameValueCollection collection)
        {
            NameValueCollection data = new NameValueCollection();

            foreach (string name in collection.AllKeys)
            {
                if (name.StartsWith(postDataKey))
                {   /// filter the form data from the collection and data that prefix's with this control stuff it into a new collection
                    data.Add(name, collection[name]);
                }
            }

            return ((postdata_loaded = ProcessPostData(data)) && (Page.IsCallback == false));
        }
        /// <summary>
        /// Process the Post Data for this control and return true if something has changed.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        protected virtual bool ProcessPostData(NameValueCollection data)
        {   /// this member to be overridden when a control needs to process post data
            return postdata_loaded;
        }

        public virtual void RaisePostDataChangedEvent()
        {   /// since this was not overridden then cancel the request
            Arguments.Cancel = true;
        }

        #endregion

        #region "field variables"
        protected readonly static object callback_key = new object();
        private bool postdata_loaded = false;
        private Templates templates = null;
        protected ScriptContainer Scripts = null;
        #endregion

        #region "field constants"
        protected const string undefined = "undefined";
        protected static string error = @"
        function(d, e, x) {
            alert(e);
        }";
        #endregion

        protected void EnsureDefaultValues()
        {
            foreach (PropertyInfo oProperty in GetType().GetProperties())
            {
                object[] attributes = null;

                if ((attributes = oProperty.GetCustomAttributes(typeof(DefaultValueAttribute), false)).Length == 0 || oProperty.GetValue(this, null) != null)
                {
                    continue;
                }

                oProperty.SetValue(this, ((DefaultValueAttribute)attributes[0]).Value, null);
            }
        }

        protected virtual void Initialize()
        {
            EnableViewState = false;                /// our controls do not use view state in the traditional sense
            Scripts = new jQNetScriptContainer(this);
            Templates = new List<Script>();
            ScriptEvents = new List<Script>();
        }

        protected override void AddParsedSubObject(object obj)
        {
            if (obj is Script)
            {
                var S = obj as Script;

                if (S.Type == jQNetScriptType.html)
                {
                    S.ID = PrefixTemplateID(S.ID);

                    RenderTemplates[S.ID] = new jQNetTemplate()
                    {
                        ID = S.ID,
                        Script = S.Body,
                        Type = String.Format("text/{0}", S.Type)
                    };
                }
                else if(S.Type == jQNetScriptType.javascript)
                {
                    Scripts[S.Event] = S.Body;
                }
            }
        }

        protected virtual string PrefixTemplateID(string id)
        {
            return id;
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            EnsureDefaultValues();

            foreach (var script in Templates)
                AddParsedSubObject(script);
            foreach (var script in ScriptEvents)
                AddParsedSubObject(script);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (Width.IsEmpty == false)
            {
                Style[HtmlTextWriterStyle.Width] = Width.ToString();
            }
            if (Height.IsEmpty == false)
            {
                Style[HtmlTextWriterStyle.Height] = Height.ToString();
            }
            if (ToolTip.IsNotNullOrEmpty())
            {
                Attributes["title"] = ToolTip;
            }

            base.Render(writer);
        }

        protected virtual void RenderItemTemplate<T>(HtmlTextWriter oWriter, T oItem, bool isTemplate)
            where T: class
        {
        }

        protected bool TypeOf<T>()
            where T: class
        {
            return (this as T) != null;
        }
    }
}
